Loop-prevention flags, O(n²) subform fixes, custom sort fields, and other patterns I keep reaching for in production Zoho Creator applications — explained with real Deluge code.
Deluge is easy to pick up and easy to write yourself into a corner with. Most Zoho Creator apps start clean — a few forms, a few workflows — and then grow into something with dozens of interdependent fields, subforms with hundreds of rows, and scripts that quietly re-trigger each other.
The patterns below aren't theoretical. Each one comes from a real production issue I hit while building multi-module Zoho Creator applications — things like an infinite-loop bug that only appeared with certain field combinations, or a history-tracking script that worked fine at 50 rows and then timed out at 500.
The fix is a boolean flag field that the script sets right before it changes the field in question. The handler checks the flag first thing — if it's true, it resets the flag and exits immediately, skipping its own logic.
// Before the script changes the field programmatically:
input.No_Script_Run = true;
input.Select_Component = updated_components;
// Inside that field's own input handler:
if(input.No_Script_Run == true)
{
input.No_Script_Run = false;
return; // script-triggered, not user-triggered — skip
}
// else: genuine user input — run normally
refreshComponentLogic(input);
This is the single most useful pattern for any form where two or more fields update each other. Without it, these mutual-update relationships are a matter of when, not if, they loop.
This flips the problem from "detect what changed by comparing two states" to "record what changed at the moment it changes" — which is both cheaper and gives you a genuine audit trail as a side effect.
Sorting subform rows in script — grouping by category, then by sub-order — usually means writing (and maintaining) custom loop logic every time the sort rules change. There's a simpler way: let Zoho's native column sort do the work.
// Group order = 1, item code = "ABC.01"
// Extract last 2 digits of the code: "01"
// Concatenate: 1 + 01 = "101"
code_suffix = right(input.Item_Code, 2);
sort_val = input.Group_Order.toString() + code_suffix;
input.Sort_Field = sort_val.toLong();
// Rows now sort natively as 101, 102, 201, 202...
// Groups stay together, items ordered within each group
Build one numeric field that already encodes your sort priority, then sort the subform column on that field — zero runtime script cost, and it scales to any number of rows.
Rather than validating "does the sum of all invoices exceed the quotation?" on every save by re-summing every related record, maintain a running balance field on the parent record. Every time a child record is created, it both reads the current balance and writes the new one.
Sometimes you want to keep "the last N values" without letting a related record grow forever — vendor rate revisions, for example. Track an edit counter, then use modulo arithmetic to decide which of N fixed slots gets overwritten next.
edit_count = input.Edit_Count;
slot = edit_count % 5;
if(slot == 0) { slot = 5; } // mod gives 0 on the 5th, 10th... edit
if(slot == 1) { input.Value_1 = new_value; }
else if(slot == 2) { input.Value_2 = new_value; }
// ...and so on through slot 5
input.Edit_Count = edit_count + 1;
No new records are created, storage stays flat, and you get a bounded, predictable history without a cleanup job.
When the same underlying data needs to appear in two different stages of a process (pre-sale and post-sale, draft and final, and so on) — but edits in one stage must never affect the other — the instinct is to copy every row. For subforms with hundreds of rows, that's wasteful and hard to keep in sync.
Instead, give the shared subform two parent-lookup fields, one for each stage. The same physical row displays in both views by default. Only when a row is actually edited in the later stage does the script create a new row tagged to that stage — untouched rows stay shared, edited rows fork automatically.
Deluge custom functions called via REST or from another module can fail in ways that are easy to miss — a missing field, an unexpected null, a permission error. Wrapping the core logic in a try/catch and writing a structured error record (function name, input snapshot, timestamp, error message) turns a silent failure into something you can actually debug later, instead of a support ticket with no trail behind it.
Every pattern above does the same basic thing: move a problem from "check for it every time" to "make it structurally impossible." That's true of the loop-prevention flag, the running balance field, the computed sort field, and the dual foreign keys — each replaces repeated runtime logic with a small piece of state that does the same job for free.
I build custom Zoho Creator applications end-to-end — from BOM-heavy modules to multi-team approval chains. Happy to talk through what you're running into.
Let's Talk ↗